﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

using System.ComponentModel;
using System.Threading;
//tarkasta että jononaikalskenta laskee rakennukset oikeassa järjestyksessä
//varastomaximit (laske rakennuksista)

namespace TravianLib
{
    public class queueItem : ICloneable
    {
        public building building;
        public DateTime resourceWaitTime;
        public TimeSpan buildWaitTime;
        public resources upgradeCosts;        
        public int order;
        public int ID;
        public queueItem(building Building, int MBLevel)
        {
            this.ID = Building.ID;
            building = Building;
            this.upgradeCosts = this.building.UpgradeCosts;
            this.buildWaitTime = this.building.BuildTime(MBLevel);
        }
        public object Clone()
        {
            return this.MemberwiseClone();
        }
    }
    public struct plusProd
    {
        public bool wood;
        public bool clay;
        public bool iron;
        public bool crop;
    }
    public struct resources
    {
        public int wood, clay, iron, crop;
        public DateTime changed;
    };
    public class VillageNew
    {
        public VillageNew(HTTPFetch hf, int newdid)
        {
            _newdid = newdid;
            _httpFetch = hf;
            hf.ResourcesUpdated += new HTTPFetch.ResourcesUpdatedHandler(resourcesUpdated);                   
                   
            for (int i = 1; i <= 40; i++)
            {
                building bd = new building(i, building.buildingType.Unknown, 0, hf);
                _buildings.Add(bd);
            }
            GetBuildings();
            Barracks = new Barracks(_httpFetch);
            Vid = 243162;
        }

        private List<building> _buildings = new List<building>();
        private List<queueItem> _queue = new List<queueItem>();
        private int _newdid = 0;

        private HTTPFetch _httpFetch;
        private resources _resources;
        
        private bdReqs _bdReq = Utils.TravianData;
        private bool _busyBuilding = false;
        private System.Timers.Timer _timer;

        public int Vid { get; private set; }
        public int Y { get; private set; }
        public int X { get; private set; }
        public plusProd PlusProduction = new plusProd() { wood = false, clay = false, iron = false, crop = false };
        public Barracks Barracks
        {
            get;
            private set;
        }
        public List<building> Buildings
        {
            get { return _buildings; }
        }
        public IEnumerable<queueItem> Queue
        {
            get
            {
                return (from c in _queue orderby c.order ascending select c);
            }
        }
        public int MBLevel
        {
            get
            {
                return (from c in this._buildings
                        where c.GID == building.buildingType.MainBuilding
                        select c.Level).SingleOrDefault();
            }
        }
        public List<building> BuildingsWithQueue
        {
            get
            {
                List<building> output = new List<building>();

                foreach (building f in _buildings)
                {
                    output.Add((building)f.Clone());
                }
                foreach (queueItem tmp in _queue)
                {
                    output[tmp.ID - 1].Level++;
                    output[tmp.ID - 1].GID = tmp.building.GID;
                }
                return output;
            }
        }
        public int CropOffset { get; set; }
        public resources Resources
        {
            get
            {
                resources prod = Production;
                double wood = _resources.wood + (System.DateTime.Now - _resources.changed).TotalHours * prod.wood;
                double clay = _resources.clay + (System.DateTime.Now - _resources.changed).TotalHours * prod.clay;
                double iron = _resources.iron + (System.DateTime.Now - _resources.changed).TotalHours * prod.iron;
                double crop = _resources.crop + (System.DateTime.Now - _resources.changed).TotalHours * prod.crop;
                resources tmp = new resources();
                tmp.wood = (int)wood;
                tmp.clay = (int)clay;
                tmp.iron = (int)iron;
                tmp.crop = (int)crop;
                return tmp;
            }
        }
        public resources Production
        {
            get
            {
                return calcProd(_buildings);
            }
        }
        public resources ProductionWithQueue
        {
            get
            {
                return calcProd(BuildingsWithQueue);
            }
        }
        public List<building> AvaibleBuildings
        {
            get
            {
                List<building> output = new List<building>();
                for (int i = 5; i <= 39; i++)
                {
                    if (i != 31 && i != 32 && i != 33 && i != 36)
                    {
                        var exists = (from c in BuildingsWithQueue
                                      where c.GID == (building.buildingType)i
                                      select c).Take(1).SingleOrDefault();

                        if (exists == null)
                        {
                            var q = (from c in _bdReq.requirements
                                     where c.GID == i
                                     select c);
                            bool prereqs = true;
                            foreach (bdReqs.requirementsRow row in q)
                            {
                                if (row.Requirement == 211 && row.Level == 211) { break; }
                                var prereq = (from c in BuildingsWithQueue
                                              where row.Requirement == (int)c.GID && row.Level <= c.Level
                                              select c).Take(1).SingleOrDefault();
                                if (prereq == null)
                                {
                                    prereqs = false;

                                }
                            }
                            if (prereqs)
                            {
                                output.Add(new building(-1, (building.buildingType)i, 0, _httpFetch));
                            }
                        }
                    }
                }
                return output;
            }
        }
        public bool BusyBuilding { get { return _busyBuilding; } }
        public DateTime QueueFinishTime { get { return calcQueueBuildTime(Buildings, Queue); } }
    
        private void GetBuildings()
        {
            GetBuildings(_httpFetch.createHTTPrequest("/dorf1.php") + _httpFetch.createHTTPrequest("/dorf2.php"));
        }
        private void GetBuildings(string HTML)
        {
            Regex rgx = new Regex("<area href=\"build.php.id=([0-9]+)\".+?title=\"(.*?)([0-9]+|\")");

            foreach (Match c in rgx.Matches(HTML))
            {

                // System.Windows.Forms.MessageBox.Show();
                var q = (from o in _buildings
                         where o.ID == int.Parse(c.Groups[1].Value)
                         select o).SingleOrDefault();

                if (q != null)
                {
                    q.GID = gidToEnum(c.Groups[2].Value);
                    if (q.GID == building.buildingType.Unknown)
                    {
                        Regex rgxUnknown = new Regex("d" + (q.ID - 18) +" g([0-9]+).*?([0-9]+)");
                        MatchCollection matches = rgxUnknown.Matches(HTML);
                        if (matches.Count > 0)
                        {
                            q.GID = (building.buildingType)int.Parse(matches[0].Groups[1].Value);
                        }
                    }
                    if (q.ID == 40)
                    {

                    }
                    else if (q.ID == 39)
                    {
                        q.GID = building.buildingType.RallyPoint;
                    }

                    if (c.Groups[3].Value == "\"")
                    {
                        q.Level = 0;
                    }
                    else
                    {
                        q.Level = ushort.Parse(c.Groups[3].Value);
                    }
                }
            }
        }

        public void Refresh() {
            GetBuildings();
            //GetTroops();
            //GetPlus();
        }

        public void moveUpInQueue(queueItem qi)
        {
            var q = (from c in _queue
                     where c.order < qi.order
                     orderby c.order descending
                     select c).Take(1).SingleOrDefault();
            if (q.building == null)
            {

            }
            else
            {
                int tmp = qi.order;
                qi.order = q.order;
                q.order = tmp;
            }
        }
        public void moveDownInQueue(queueItem qi)
        {
            queueItem q = (from c in _queue
                           where c.order > qi.order
                           orderby c.order ascending
                           select c).Take(1).SingleOrDefault();
            if (q.building == null)
            {

            }
            else
            {
                int tmp = qi.order;
                qi.order = q.order;
                q.order = tmp;
            }
        }
        public void addToQueue(building bd)
        {
            int MBLevel = this.MBLevel;
            queueItem tmp = new queueItem(bd, MBLevel);

            if (bd.GID == building.buildingType.RallyPoint)
            {
                tmp.ID = 39;
            }
            else if (bd.GID == building.buildingType.CityWall)
            {
                tmp.ID = 40;
            }
            else if (bd.ID == -1)
            {
                tmp.ID = (from c in BuildingsWithQueue
                          where c.GID == building.buildingType.Free
                          select c.ID).Take(1).SingleOrDefault();
            }
            else
            {
                tmp.ID = bd.ID;
            }
            try
            {
                if ((int)tmp.building.GID < 5)
                {
                    queueItem tmpQI = (from c in _queue
                                 where c.order < 0
                                 orderby c.order descending
                                 select c).Take(1).FirstOrDefault();
                    if (tmpQI == null)
                    {
                        tmp.order = -1000;
                    }
                    else
                    {
                        tmp.order = tmpQI.order + 1;
                    }
                }
                else
                {
                    tmp.order = (from c in _queue
                                 select c.order).Max() + 1;
                }
            }
            catch
            {
                tmp.order = 1;
            }

            _queue.Add(tmp);
            _queue = (from c in _queue
                      orderby c.order ascending
                      select c).ToList();
        }
        public void removeFromQeueu(building building)
        {
            var q = (from c in _queue
                     where c.building == building
                     select c).Take(1).SingleOrDefault();
            //if (q.building.GID == building.buildingType.Free)
            //{

            //}
            _queue.Remove(q);
        }

        public bool doQeueu()
        {
            var target = (from c in _queue
                          orderby c.order
                          select c).Take(1).SingleOrDefault();

            if (target != null && _busyBuilding == false)
            {
                string HTML = target.building.upgrade(Resources, target.ID);
                if (HTML != "")
                {                
                    startTimer(HTML);
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return false;
            }
        }
        public bool buildResourceFields()
        {
            var q = (from c in _buildings
                     where (int)c.GID <= 4 && c.canUpgrade(Resources) && c.enoughFoodToUpgrade(Production)
                     orderby c.Level ascending
                     select c).Take(1).SingleOrDefault();
            if (q != null && !_busyBuilding)
            {
                _queue.Clear();
                addToQueue(q);
                doQeueu();
                return true;
            }
            return false;
        }
        public void optimizeQueuev2()
        {
            List<queueItem> bestQueue = new List<queueItem>();
            foreach (queueItem qi in _queue)
            {
                bestQueue.Add((queueItem)qi.Clone());
            }
            DateTime fastest = calcQueueBuildTime(_buildings,_queue);
            bool cont = true;
            while(cont) {
                cont = false;
                for (int i = 1; i < 5; i++)
                {
                    building bd = (from c in BuildingsWithQueue
                                   where (int)c.GID == i
                                   orderby c.Level ascending
                                   select c).Take(1).SingleOrDefault();
                    addToQueue(bd);
                    DateTime dResult = calcQueueBuildTime(_buildings, _queue);
                   
                    if (dResult < fastest)
                    {
                        cont = true;
                        bestQueue = new List<queueItem>();
                        foreach (queueItem qi in _queue)
                        {
                            bestQueue.Add((queueItem)qi.Clone());
                        }
                        fastest = dResult;                      
                    }
                    removeFromQeueu(bd);
                }
                _queue = new List<queueItem>();
                foreach (queueItem qi in bestQueue)
                {
                    _queue.Add((queueItem)qi.Clone());
                }
            }
           // List<queueItem> bestQueue = new List<queueItem>();
           // List<queueItem> newQeue = (from c in this.queue_
                 
           //          select c).ToList();
           // DateTime fastest = DateTime.MaxValue;
           //int MBLevelTmp = this.MBLevel;
           //bool cont = true;

           //List<building> tmpBuildings = new List<building>();
           //foreach (building f in buildings_)
           //{
           //    tmpBuildings.Add((building)f.Clone());
           //}

           //while (cont)
           //{
           //    cont = false;
           //     fastest = calcQueueBuildTime(buildings_, newQeue);
           //    for (int i = 1; i < 5; i++)
           //    {
           //        building bd = (from c in tmpBuildings
           //                       where (int)c.GID == i
           //                       orderby c.Level ascending
           //                       select c).Take(1).SingleOrDefault();
           //        newQeue.Insert(0,new queueItem(bd, MBLevelTmp));
           //        for (int o= 0;o < newQeue.Count; o++)
           //        {
           //            newQeue.ElementAt(o).order = o;
           //        }
           //        DateTime dResult = calcQueueBuildTime(buildings_, newQeue);
           //        if (fastest > dResult)
           //        {
           //            bestQueue = new List<queueItem>();
           //            foreach (queueItem qi in newQeue)
           //            {
           //                bestQueue.Add((queueItem)qi.Clone());
           //            }
           //            fastest = dResult;
           //            cont = true;
           //        }
           //        newQeue.RemoveAt(0);
           //    }
           //    newQeue = new List<queueItem>();
           //    foreach (queueItem qi in bestQueue)
           //    {
           //        newQeue.Add((queueItem)qi.Clone());
           //    }
           //}
           //queue_ = bestQueue;
        }
        public void optimizeQueue(System.Windows.Forms.Label lbl)
        {

            IEnumerable<queueItem> bestQueue = new List<queueItem>();
            

            DateTime fastest = DateTime.MaxValue;
           
            int fc = fact(_queue.Count);

            int i = 0;
            DateTime start = DateTime.Now;

            Parallel.ForEach(PermuteUtils.Permute<queueItem>(_queue,_queue.Count), queueCur =>
            {
                i++;
                if (!lbl.InvokeRequired)
                {
                    lbl.Text = i + "/" + fc;
                }

                DateTime time = calcQueueBuildTime(_buildings, queueCur);
                if (fastest > time)
                {                    
                    bestQueue = queueCur;
                    fastest = time;                   
                }
            });

            for (int o = 0; o < bestQueue.Count(); o++)
            {
                bestQueue.ElementAt(o).order = o;
            }

           System.Diagnostics.Debug.Print((DateTime.Now - start).TotalSeconds.ToString());
       
        }     

        private resources calcProd(List<building> buildings)
        {
            resources tmp = new resources();

            IEnumerable<int> list = (from c in buildings
                     join o in _bdReq.costs on
                     new { KEY1 = (ushort)c.GID, KEY2 = c.Level }
                     equals
                     new { KEY1 = o.GID, KEY2 = o.Level }
                     where (ushort)c.GID < 5
                     group o by o.GID into g
                     orderby g.Key
                     select  g.Sum(c => c.Benefit)                                          
                     );
            
                tmp.wood = list.ElementAt(0);
                tmp.clay = list.ElementAt(1);
                tmp.iron = list.ElementAt(2);
                tmp.crop = list.ElementAt(3);

                tmp.wood = (int)((double)tmp.wood * (PlusProduction.wood ? 1.25 : 1));
                tmp.clay = (int)((double)tmp.clay * (PlusProduction.clay ? 1.25 : 1));
                tmp.iron = (int)((double)tmp.iron * (PlusProduction.iron ? 1.25 : 1));
                tmp.crop = (int)((double)tmp.crop * (PlusProduction.crop ? 1.25 : 1));

                tmp.crop -= (from c in buildings
                             join o in _bdReq.costs
                             on
                             new { KEY2 = (ushort)c.GID }
                             equals
                             new { KEY2 = o.GID }
                             where o.Level <= c.Level
                             select (int)o.Consumption).Sum();
                tmp.crop -= CropOffset;
                
           
            return tmp;
        }
        private DateTime calcQueueBuildTime(List<building> buildings, IEnumerable<queueItem> queue)
        {
 System.IO.TextWriter tw = new System.IO.StreamWriter(@"queuetime.csv",false);

            resources res = Resources;

            List<building> tmpBuildings = new List<building>();
            foreach (building f in buildings)
            {
                tmpBuildings.Add((building)f.Clone());
            }

            DateTime result = DateTime.Now;

            result = DateTime.Now;
            tw.WriteLine("START" + ";" +result.ToString());
            foreach (queueItem qi in queue)
            {
                try
                {
                    resources prod = calcProd(tmpBuildings);
                    tw.WriteLine("START" + ";" + result.ToString() + ";" + qi.building.GID.ToString() + ";" + qi.building.Level + ";" + prod.wood + ";" + prod.clay + ";" + prod.iron + ";" + prod.crop + ";" + res.wood + ";" + res.clay + ";" + res.iron + ";" + res.crop);
                    if (prod.crop == 0)
                    {
                        result = DateTime.MaxValue;
                        break;
                    }
                    double waitTime = qi.building.resourceWaitTime(res, prod);
                    tw.WriteLine("WAITTIME" + ";;" + waitTime.ToString("F"));
                    result = result.AddHours(waitTime);  
                    //qi.buildWaitTime = (DateTime.Now.AddHours(waitTime));
                    res.wood += (int)(prod.wood * waitTime);
                    res.clay += (int)(prod.clay * waitTime);
                    res.iron += (int)(prod.iron * waitTime);
                    res.crop += (int)(prod.crop * waitTime);
                    tw.WriteLine("AFTRESWAIT" + ";" + result.ToString() + ";" + qi.building.GID.ToString() + ";" + qi.building.Level + ";" + prod.wood + ";" + prod.clay + ";" + prod.iron + ";" + prod.crop + ";" + res.wood + ";" + res.clay + ";" + res.iron + ";" + res.crop);

                    //tämän voit optimoida
                   // resources builCosts = qi.building.UpgradeCosts;                    
                    res.wood -= qi.upgradeCosts.wood;
                    res.clay -= qi.upgradeCosts.clay;
                    res.iron -= qi.upgradeCosts.iron;
                    res.crop -= qi.upgradeCosts.crop;

                    tw.WriteLine("STARTBUILD" + ";" + result.ToString() + ";" + qi.building.GID.ToString() + ";" + qi.building.Level + ";" + prod.wood + ";" + prod.clay + ";" + prod.iron + ";" + prod.crop + ";" + res.wood + ";" + res.clay + ";" + res.iron + ";" + res.crop);

                    qi.resourceWaitTime = result;
                    result = result.Add(qi.buildWaitTime);
                    res.wood += (int)(prod.wood * ((float)qi.buildWaitTime.TotalHours));
                    res.clay += (int)(prod.clay * ((float)qi.buildWaitTime.TotalHours));
                    res.iron += (int)(prod.iron * ((float)qi.buildWaitTime.TotalHours));
                    res.crop += (int)(prod.crop * ((float)qi.buildWaitTime.TotalHours));
                    tw.WriteLine("AFTERBUILD" + ";" + result.ToString() + ";" + qi.building.GID.ToString() + ";" + qi.building.Level + ";" + 0 + ";" + 0 + ";" + 0 + ";" + 0 + ";" + res.wood + ";" + res.clay + ";" + res.iron + ";" + res.crop);
                    tw.WriteLine("");
                    //tähän väliin RAKENNUSAIKA ja SINÄ AIKANA SAATAVAT RESURSSIT
                    (from c in tmpBuildings
                     where c.ID == qi.ID
                     select c).SingleOrDefault().buildingDone(qi.ID, qi.building.GID);
                }
                catch
                {
                    //return DateTime.MinValue;
                }
            }
            tw.WriteLine("FINAL Result : " + result.ToString());
            tw.Close();
            return result;
        }

        void tmpTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            int test = 23;
            test++;
            _httpFetch.createHTTPrequest("/dorf1.php");

            var target = (from c in _queue
                          orderby c.order
                          select c).Take(1).SingleOrDefault();
            target.building.buildingDone(target.ID, target.building.GID);
            _buildings[target.building.ID - 1] = target.building;

            removeFromQeueu(target.building);

            _busyBuilding = false;
            BuildingComplete(new object(), new EventArgs());
        }
        private void startTimer(string HTML)
        {
            _timer = new System.Timers.Timer();
            _timer.Interval = TimeSpan.Parse(new Regex("timer1\">([0-9]+:[0-9]{2}:[0-9]{2})").Matches(HTML)[0].Groups[1].Value).TotalMilliseconds;
            _timer.Elapsed += new System.Timers.ElapsedEventHandler(tmpTimer_Elapsed);
            _timer.AutoReset = false;
            _timer.Enabled = true;
            _busyBuilding = true;
            BuildingStarted(this, new EventArgs());
        }

        public event EventHandler BuildingComplete;
        public event EventHandler BuildingStarted;
        private void resourcesUpdated(resources resources)
        {
            _resources = resources;
        }

        private int fact(int n)
        {
            if (n == 0)
            {
                return 1;
            }

            else
            {
                return n * fact(n - 1);
            }
        }
        private building.buildingType gidToEnum(string gid)
        {
            if (gid == fi_FI.Woodcutter)
            {
                return building.buildingType.Woodcutter;
            }
            else if (gid == fi_FI.Marketplace)
            {
                return building.buildingType.Marketplace;
            }
            else if (gid == fi_FI.Barracks)
            {
                return building.buildingType.Barracks;
            }
            else if (gid == fi_FI.Cranny)
            {
                return building.buildingType.Cranny;
            }
            else if (gid == fi_FI.ClayPit)
            {
                return building.buildingType.ClayPit;
            }
            else if (gid == fi_FI.IronMine)
            {
                return building.buildingType.IronMine;
            }
            else if (gid == fi_FI.Cropland)
            {
                return building.buildingType.Cropland;
            }
            else if (gid == fi_FI.CityWallSite)
            {
                return building.buildingType.CityWallSite;
            }
            else if (gid == fi_FI.RallyPoint)
            {
                return building.buildingType.RallyPoint;
            }
            else if (gid == fi_FI.Rally_Point_Site)
            {
                return building.buildingType.RallyPointSite;
            }
            else if (gid == fi_FI.Freesite)
            {
                return building.buildingType.Free;
            }
            else if (gid == fi_FI.Warehouse)
            {
                return building.buildingType.Warehouse;
            }
            else if (gid == fi_FI.MainBuilding)
            {
                return building.buildingType.MainBuilding;
            }
            else if (gid == fi_FI.Granary)
            {
                return building.buildingType.Granary;
            }
            else if (gid == fi_FI.Academy)
            {
                return building.buildingType.Academy;
            }
            else if (gid == fi_FI.Embassy)
            {
                return building.buildingType.Embassy;
            }
            else if (gid == fi_FI.Hero_s_Mansion)
            {
                return building.buildingType.HerosMansion;
            }
            else
            {
                return building.buildingType.Unknown;
            }
        }
    }
}
